﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace ChartEdit
{
    public enum ToolType
    {
        Pointer,
        Move,
        PaintBrush,
        Deleter,
        PatternPaster,
        PatternPasterOffset,
        Selection,
        VerticalSelection,
        EventSelection,
        Extender,
        Stretcher,
        Anchor,
        Event,
        BPM,
        Section,
        Marker
    }
    public enum ActionType
    {
        Pointer,
        Move,
        PaintBrush,
        Deleter,
        PatternPaster,
        PatternPasterOffset,
        Selection,
        VerticalSelection,
        EventSelection,
        Extender,
        Stretcher,
        Anchor,
        Event,
        BPM,
        Section,
        Marker,
        Nothing,
        Dragging
    }

    public class Editor
    {

        #region Variables

        public EditForm parentForm;
        // Rendering related
        #region Drawing
        public int Width;
        public int Height;

        //Fretboard dimension variables and methods
        private float fbX1=0;
        public float FbX1
        {
            get { return fbX1; }
            set
            {
                fbX1 = value;
                fbX = value * Width;
                fbXB = (value + (fbXBuffer * (fbWidth / Theme.baseWidth))) * Width;
                fbWidth = (fbX2 - value) * Width;
                fbWidthB = (fbX2 - value - (fbXBuffer * 2 * (fbWidth / Theme.baseWidth))) * Width;
                //currentTheme.Resize((int)FbWidth);
            }
        }
        private float fbX2=1;
        public float FbX2
        {
            get { return fbX2; }
            set
            {
                fbX2 = value;
                fbWidth = (value-fbX1) * Width;
                fbXB = (fbX1 + (fbXBuffer * (fbWidth / Theme.baseWidth))) * Width;
                fbXR = value * Width;
                fbWidthB = (value - fbX1 - (fbXBuffer*2*(fbWidth/Theme.baseWidth))) * Width;
                //currentTheme.Resize((int)FbWidth);
            }
        }
        //public float FbX2;//obsolete

        private float fbX;
        public float FbX { get { return fbX; } }
        private float fbXR;
        public float FbXR { get { return fbXR; } }
        private float fbXB;
        public float FbXB { get { return fbXB; } }
        private float fbWidth;
        public float FbWidth { get { return fbWidth; } }
        private float fbWidthB;
        public float FbWidthB { get { return fbWidthB; } }
        private float fbXBuffer;

        public PreRenderer PR;
        public Renderer2 R;

        #endregion

        #region Transforming and Scrolling
        // Tranforming and Scrolling
        public OffsetTransformer OT;
        public TSManager tSM;
        public Clapper clapper;
        public int lastEventOffset = 0; //To make it easy to scroll to the end

        public float SecPerH
        {
            get
            {
                return sph;
            }
            set
            {
                sph = value;
                SphScaled = value/Height;
            }
        } //Vertical scaling
        private float sph;
        public float SphScaled; //SecPerH*height
        public float COffset
        {
            get { return cOffset; }
            set {
                cOffset = value;
                QOffset = tSM.NearestSnapPointBeat(Quantization, OT.GetOffset(cOffset));
                parentForm.VScrollChartValue = (int)(value * 1000f);
                if(!song.IsPlaying())song.SetPosition((uint)(value * 1000f)); 
                }
        } 
        private float cOffset;

        #endregion

        public Chart chart;
        public NoteTrack noteTrack;
        public ThemeManager themeManager;
        public Theme currentTheme;

        public SubEditor SE;
        public List<KMKey> KeysPressed = new List<KMKey>();
        public List<KMKey> KeysPressedOrdered = new List<KMKey>();
        private bool keyTrigger = false;
        private bool keyChanged = false;

        public int QOffset = 0;

        public Audio song;
        public bool debugLoop;

        //Editor specific
        public volatile int Quantization=48;
        //Tool specific
        public MTool MainTool = MTool.Pointer;
        public ToolType tool = ToolType.Pointer;
        public ActionType action = ActionType.Nothing;
        public ObjectType o = ObjectType.Nothing;

        public volatile bool MouseMove;
        public volatile int MouseX;
        public volatile int MouseY;
        public volatile bool MouseDown;
        public volatile bool MouseUp;
        private MouseManager mm;
        public ClickData CData = new ClickData();
        private bool clicking;
        //Resizing
        public System.Diagnostics.Stopwatch rSW = new System.Diagnostics.Stopwatch();


        #endregion

        public void Resize(int width, int height)
        {
            FbX1 *= (float)this.Width / Math.Max((float)width,10f);
            FbX2 *= (float)this.Width / Math.Max((float)width,10f);
            //if (FbX2 > 0.95f) FbX2 = 0.95f;
            //if (FbX1 > FbX2 - fbXBuffer * 2.5) FbX1 = FbX2 - fbXBuffer * 2.5f;
            fbXBuffer *= ((float)this.Width / (float)width);
            this.Width = width;
            this.Height = height;
            //FbX2 = FbX2;
            //FbX1 = FbX1;
            currentTheme.Resize((int)FbWidth);
            R.Resize();
        }

        public void Shift(int offset)
        {
            cOffset = OT.GetTime(QOffset + offset);
            QOffset = tSM.NearestSnapPointBeat(Quantization, OT.GetOffset(cOffset));
            parentForm.VScrollChartValue = (int)(cOffset * 1000f);
            song.SetPosition((uint)(cOffset * 1000));
        }

        public void Think()
        {
            lock (R)
            {

                //Resize fretboard
                if (rSW.IsRunning & rSW.Elapsed.Milliseconds > 10)
                {
                    currentTheme.Resize((int)FbWidth);
                    rSW.Reset();
                    rSW.Stop();
                }

                if (song.IsPlaying())
                {
                    song.Play();
                    UpdateTrackPosition();
                }
                else
                {
                    float m = Math.Max(0, Math.Min(PR.Lag * 10, 1));
                    m = (float)Math.Sqrt((double)m);
                    //QOffset = tSM.NearestSnapPointBeat(Quantization,OT.GetOffset(COffset));
                    COffset = cOffset * (1 - m) + OT.GetTime(QOffset) * m;
                }
                //QOffset = tSM.NearestSnapPointBeat(Quantization,OT.GetOffset(COffset));
                if (parentForm.WindowState != System.Windows.Forms.FormWindowState.Minimized)
                {
                    KeyThink();
                    PR.CalculateRenderingInfo(true);
                    MouseEvents();
                    R.Render(RenderingMode.HighQuality);
                }
            }
        }
        public void KeyThink()
        {
            SE.Think(keyTrigger,keyChanged);
            /*if (noteTrack != null)
            {
                //if (keyChanged)
                {
                    List<Note> nList = new List<Note>();
                    foreach (KMKey k in KeysPressed)
                    {
                        if (k.Type == KeyType.Note)
                        {
                            nList.Add(new Note(Globals.Instance.GameProperties[0].
                                TrackProperties[noteTrack.Name].NoteProperties[k.Value]));
                            nList.Last().Offset = QOffset;
                        }
                    }
                    AddNotes(nList);
                }
            }*/
            keyTrigger = false;
            keyChanged = false;
        }


        public void CreateDummyNoteTrack()
        {
            noteTrack = new NoteTrack();

            noteTrack.Add( new Note() );
            noteTrack[ 0 ].Offset = 0;
            noteTrack[ 0 ].Fret = 0;
            noteTrack[ 0 ].Type = NoteType.Regular;
            noteTrack[ 0 ].Length = 0;
            noteTrack[0].IsANote = false;
        }
        public void LoadTrack(string track)
        {
            noteTrack = chart.NoteTracks[track];

            if (noteTrack == null)
                return; // don't handle a missing track

            //if (noteTrack.Count() == 0)
                //CreateDummyNoteTrack();

            foreach (NoteProp n in Globals.Instance.GameProperties[0].TrackProperties[track].NoteProperties)
            {
                noteTrack.AssignProperty(n.Fret, n.IsNote, n.FlipsHOPO, n.ForcesHOPO, n.ForcesStrum, n.PointValue);
            }
            noteTrack.Evaluate(chart);
            noteTrack.GenerateFretList();

            currentTheme = themeManager[noteTrack.Instrument.ToLower()];
            foreach (Theme t in themeManager)
            {
                t.Resize((int)fbWidth);
            }
        }

        public void PressKeys(List<KMKey> keys, List<KMKey> keysOrdered)
        {
            keyTrigger = true;
            if (keys.Count() != KeysPressed.Count())
            {
                keyChanged = true;
                KeysPressed = keys;
                KeysPressedOrdered = keysOrdered;
                return;
            }
            for (int i = 0; i < keys.Count(); i++)
            {
                if (keys[i] != KeysPressed[i])
                {
                    keyChanged = true;
                    KeysPressed = keys;
                    KeysPressedOrdered = keysOrdered;
                    return;
                }
            }
            KeysPressed = new List<KMKey>();
            KeysPressed = keys;
            keyChanged = false;
        }

        public void DeleteNote(Note n)
        {
            int index = OT.nearNIndex(n.Offset, noteTrack);
            int index2 = OT.nearNIndex(n.Offset, noteTrack.NoteList(n.Fret));

            //noteTrack.RemoveAt(index);
            //noteTrack.noteList[n.Fret].RemoveAt(index2);
            noteTrack.Remove(n);
            noteTrack.noteList[n.Fret].Remove(n);

            int start = Math.Max(index-noteTrack.HighestFret, 0);
            int start2 = Math.Max(start, 1);
            int end = Math.Min(index + noteTrack.HighestFret, noteTrack.Count() - 1);
            noteTrack.Evaluate(chart, start, start2, end);
        }
        public void AddNotes(List<Note> nl)
        {
            if (noteTrack == null)
                return;
            int start=-1;
            int end=-1;
            int sD = Int32.MaxValue;
            int eD = Int32.MinValue;
            foreach (Note n in nl)
            {
                int index = OT.nextNIndex(n.Offset, noteTrack);
                int index2 = OT.nextNIndex(n.Offset, noteTrack.NoteList(n.Fret));

                if (index2 == -1) index2 = 0;
                if (index == -1) index = 0;

                bool dupe = false;//Check to see if the note is a dupe before adding
                for (int i = index; i > 0; i--)
                {
                    if (noteTrack[i].Offset < n.Offset)
                        break;
                    if (noteTrack[i].Offset > n.Offset)
                        continue;
                    if (noteTrack[i].Fret == n.Fret)
                    {
                        dupe = true;
                        break;
                    }
                }
                for (int i = index+1; i < noteTrack.Count(); i++)
                {
                    if (noteTrack[i].Offset > n.Offset)
                        break;
                    if (noteTrack[i].Offset < n.Offset)
                        continue;
                    if (noteTrack[i].Fret == n.Fret)
                    {
                        dupe = true;
                        break;
                    }
                }
                if (dupe)
                    continue;//continue to next note if dupe
                noteTrack.Insert(index, n);
                noteTrack.FillNoteList(n.Fret);

                noteTrack.NoteList(n.Fret).Insert(index2, n);
                if (n.Offset < sD)
                {
                    sD = n.Offset;
                    start = index;
                }
                if (n.Offset > eD)
                {
                    eD = n.Offset;
                    end = index;
                }
            }
            if (start == -1 && end == -1)
                return;
            start=Math.Max(start-noteTrack.HighestFret,0);
            int start2=Math.Max(start,1);
            end=Math.Min(end+noteTrack.HighestFret,noteTrack.Count()-1);
            noteTrack.Evaluate(chart, start, start2, end);
        }


        private void resizeTimer(int ms)
        {
            if (!rSW.IsRunning)
                rSW.Start();
        }


        public void MouseEvents()
        {
            if (MouseMove || song.IsPlaying())
            {
                mouseMove(MouseX, MouseY);
                MouseMove = false;
            }
            if (MouseDown)
            {
                mouseDown(MouseX, MouseY, 0, 0);
                MouseDown = false;
                return;
            }
            if (MouseUp)
            {
                mouseUp(MouseX, MouseY);
                MouseUp = false;
                return;
            }
        }

        private void mouseDown(int mX, int mY, int button, int scrollwheel)
        {
            if (!clicking)
            {
                /*
                float offset = COffset + (SecPerH * (float)(PR.YHit - mY)) / Height;
                int fret = mm.GetFret((float)mX);
                List<Note> nl = new List<Note>();
                for (int i = 0; i < 8; i++ )
                {
                    if (noteTrack == null)
                        break;
                    NoteProp np = Globals.Instance.GameProperties[0].TrackProperties[noteTrack.Name].NoteProperties[fret];
                    Note n = new Note();
                    n.Type = NoteType.Regular;
                    n.Fret = fret;
                    n.IsANote = np.IsNote;
                    n.FlipsHOPO = np.FlipsHOPO;
                    n.ForcesHOPO = np.ForcesHOPO;
                    n.ForcesStrum = np.ForcesStrum;
                    n.PointValue = np.PointValue;
                    n.Offset = OT.GetOffset(offset) + i * chart.HalfResolution;
                    nl.Add(n);
                }*/
                
                //AddNotes(nl);
                CData = mm.GetClickData(mX, mY);
                foreach (RendererNote rn in CData.Notes)
                {
                    DeleteNote(rn.Note);
                }
                o = mm.GetObjectType(mX, mY);
                //if (o == ObjectType.FretboardRight)
                //    System.Windows.Forms.MessageBox.Show("IT WORKS");


            }
            clicking = true;
        }
        private void mouseMove(int x, int y)
        {
            if (clicking)
            {
                if (CData.ContainsType[(byte)ObjectType.FretboardLeft])
                {
                    float f = FbX1;
                    FbX1 = Math.Max(Math.Min(((float)x) / Width, FbX2 - fbXBuffer * 2.5f), 0);
                    if(FbX1!=f)
                        resizeTimer(10);
                }
                if (CData.ContainsType[(byte)ObjectType.FretboardRight])
                {
                    float f = FbX2;
                    FbX2 = Math.Min(Math.Max(((float)x) / Width, FbX1 + fbXBuffer * 2.5f), 1);
                    if(FbX2!=f)
                        resizeTimer(10);  
                }
                /*switch (o)
                {
                    case (ObjectType.FretboardLeft):
                        FbX1 = Math.Max(Math.Min(((float)x) / Width,FbX2-fbXBuffer*2.5f),0);
                        resizeTimer(10);
                        //currentTheme.Resize((int)FbWidth);
                        break;
                    case(ObjectType.FretboardRight):
                        FbX2 = Math.Min(Math.Max(((float)x) / Width, FbX1 + fbXBuffer * 2.5f),1);
                        resizeTimer(10);  
                        //currentTheme.Resize((int)FbWidth);
                        break;

                }*/
            }
            else
            {
                CData = mm.GetClickData(x, y);
            }
        }
        private void mouseUp(int x, int y)
        {
            if (clicking)
            {
                clicking = false;
                switch (o)
                {
                    case (ObjectType.FretboardLeft):
                    case (ObjectType.FretboardRight):
                        currentTheme.Resize((int)FbWidth);
                        break;
                }

                o = ObjectType.Nothing;
            }
        }

        public void UpdateTrackPosition()
        {
            if (song.IsPlaying())
            {
                COffset = ((float)song.Position() / 1000f);
            }
        }



        #region Constructor
        public Editor(int ScreenWidth, int ScreenHeight, Chart chart, EditForm parentForm)
        {
            this.parentForm = parentForm;
            this.Height = ScreenHeight;
            this.Width = ScreenWidth;
            fbXBuffer = 0.0666f;
            FbX1 = 0.1f;
            FbX2 = 0.65f;

            SE = new SubEditor(this);
            themeManager = new ThemeManager();
            themeManager.LoadAll();

            currentTheme = themeManager["default"];
            currentTheme.Resize((int)FbWidth);

            this.chart = chart;

            song = new Audio( Path.Combine( chart.LoadPath, chart.Song[ "MusicStream" ].Value ) );
            song.DefineSyncSounds(
                Path.Combine(System.Windows.Forms.Application.StartupPath, "Misc/clap.wav"),
                Path.Combine(System.Windows.Forms.Application.StartupPath, "Misc/clap.wav"));
            OT = new OffsetTransformer(chart);
            tSM = new TSManager(chart);
            clapper = new Clapper(this);
            //CreateDummyNoteTrack();
            //offsetTransform = new OffsetTransformer(debugOffsets, debugBPMS);
            //offsetTransform.CreateOffsets();

            //CreateTestElements(); //Chart elements until we have xml configs to replace them




            this.SecPerH = 1f; //It takes "1f" second to traverse an entire screen at normal speed
            //This value is modifiable to make tracks easier or harder to read
            this.COffset = 0f;

            //create renderer
            PR = new PreRenderer(this);
            R = new Renderer2(this,PR);
            mm = new MouseManager(this);
        } 
        #endregion




        public void PlaySong()
        {
            song.Play();
        }

        public void StopSong()
        {
            song.Pause();
        }
    }
}